#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include "do_test.h"
#include "pcie_mem.h"

enum do_bar0_offset_t {
    DO_CMD_BAR0_OFFSET = 0x08,          /* DO 命令寄存器偏移 */
    DO_SET_ALL_BAR0_OFFSET = 0x34,      /* 设置全部 DO 寄存器偏移 */
    DO_GET_ALL_BAR0_OFFSET = 0x4C,      /* 读取全部 DO 寄存器偏移 */
    DO_SET_MODE_BAR0_OFFSET = 0x54,     /* 设置 DO 模式寄存器偏移 */
    DO_SET_ID_BAR0_OFFSET = 0x58,       /* 设置 DO ID 寄存器偏移 */
    DO_PULSE_NUM_BAR0_OFFSET = 0x5C,    /* 设置 DO 脉冲数寄存器偏移 */
    DO_PULSE_WIDTH_BAR0_OFFSET = 0x7C,  /* 设置 DO 脉冲宽度寄存器偏移 */
};

enum do_cmd_t {
    START_DO_CMD = 0x5555,              /* 启动 DO 命令 */
    STOP_DO_CMD = 0x6666,               /* 停止 DO 命令 */
};

enum do_pulse_shift_t {
    DO_PULSE_WITH_SHIFT = 16,           /* DO 脉冲宽度左移位数 */
    DO_PULSE_CYCLE_SHIFT = 0,           /* DO 脉冲周期左移位数 */
};

#define DO_MAX_CHANNEL        15        /* 最大通道号 */

struct do_info_t {
    uint8_t channel;                    /* 通道号 */
    uint8_t channel_min;                /* 最小通道号 */
    uint8_t channel_max;                /* 最大通道号 */
    uint32_t value;                     /* DO 输出值 */
    uint32_t value_min;                 /* 最小 DO 输出值 */
    uint32_t value_max;                 /* 最大 DO 输出值 */
};

static void set_all_do(struct pcie_mem_ctx *ctx, uint8_t channel, uint32_t value)
{
    volatile uint32_t *addr;
    uint32_t reg_val = 0;

    if (!ctx->map_base) {
        printf("错误: 内存未映射\n");
        return;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    if (channel <= DO_MAX_CHANNEL) {
        /* 注意：这里需要将字节偏移转换为 uint32_t 偏移 */
        reg_val = *(addr + (DO_SET_ALL_BAR0_OFFSET / sizeof(uint32_t)));
        reg_val &= ~(0x1 << channel);
        reg_val |= (value & (0x1 << channel));
        *(volatile uint32_t *)((uintptr_t)addr + DO_SET_ALL_BAR0_OFFSET) = reg_val;
    } else {
        *(volatile uint32_t *)((uintptr_t)addr + DO_SET_ALL_BAR0_OFFSET) = value;
    }

    printf("写入: reg=0x%04X, val=0x%08X\n",
           DO_SET_ALL_BAR0_OFFSET, value);
}

static uint32_t get_all_do(struct pcie_mem_ctx *ctx)
{
    volatile uint32_t *addr;
    uint32_t value;

    if (!ctx->map_base) {
        printf("错误: 内存未映射\n");
        return 0;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    /* 注意：这里需要将字节偏移转换为 uint32_t 偏移 */
    value = *(addr + (DO_GET_ALL_BAR0_OFFSET / sizeof(uint32_t)));

    printf("读取: reg=0x%04X, val=0x%08X\n",
            DO_GET_ALL_BAR0_OFFSET, value);

    return value;
}

// static uint32_t get_all_do_test(struct pcie_mem_ctx *ctx, struct do_info_t do_info)
// {
//     uint32_t reg_val = 0;

//     if ((do_info.channel >= do_info.channel_min) && (do_info.channel <= do_info.channel_max)) {
//         reg_val = get_all_do(ctx);
//         reg_val = (reg_val >> do_info.channel) & 0x1;
//         printf("读取通道 %d 的 DO 输出值为 0x%04X\n", do_info.channel, reg_val);
//     } else {
//         reg_val = get_all_do(ctx);
//         printf("读取全部 DO 输出值为 0x%04X\n", reg_val);
//     }
//     return reg_val;
// }

static void set_all_do_test(struct pcie_mem_ctx *ctx, struct do_info_t do_info)
{
    uint32_t reg_val = 0;
    uint8_t i = 0;

    if ((do_info.channel >= do_info.channel_min) && (do_info.channel <= do_info.channel_max)) {
        reg_val = do_info.value << do_info.channel;
        set_all_do(ctx, do_info.channel, reg_val);
        printf("设置通道 %d 的 DO 输出值为 %d\n", do_info.channel, do_info.value);
    } else {
        reg_val = do_info.value;
        set_all_do(ctx, do_info.channel, reg_val);
        printf("设置全部 DO 输出值为 0x%04X\n", do_info.value);
    }
    usleep(100000);
    reg_val = get_all_do(ctx);
    printf("读取全部通道的 DO 输入值为:\n");
    for (i = 0; i <= do_info.channel_max; i++) {
        printf("bit%02d ", i);
    }
    printf("\n");
    for (i = 0; i <= do_info.channel_max; i++) {
        printf("val:%1d ", (reg_val >> i) & 0x1);
    }
    printf("\n");
}

static int usage(int argc, char *argv[])
{
    if ((argc < 3) || (argc > 4)) {
        printf("用法: %s %s channel value\n", argv[0], argv[1]);
        printf("示例: %s %s 0 1\n", argv[0], argv[1]);
        printf("用法: %s %s value\n", argv[0], argv[1]);
        printf("示例: %s %s 0xFFFF\n", argv[0], argv[1]);
        return EXIT_FAILURE;
    } else {
        return EXIT_SUCCESS;
    }
}

int do_test(int argc, char *argv[])
{
    int ret = 0;
    const char *device_address = PCIE_DO_DEVICE_ADDRESS;
    uint32_t base_addr = PCIE_DO_BASE_ADDR_BAR0;
    size_t map_size = PCIE_DO_BAR0_MAP_SIZE;
    struct pcie_mem_ctx ctx = {
        .fd = -1,
        .map_base = NULL,
        .phys_base = 0,
        .map_size = 0
    };
    struct do_info_t do_info = {
        .channel = 0,
        .channel_min = 0,
        .channel_max = DO_MAX_CHANNEL,
        .value = 0,
        .value_min = 0,
        .value_max = 0x0000FFFF,
    };

    if (EXIT_SUCCESS != usage(argc, argv)) {
        return EXIT_FAILURE;
    }

    if (argc == 4) {
        do_info.channel = atoi(argv[2]);
        if (strncmp(argv[3], "0x", 2) == 0 || strncmp(argv[3], "0X", 2) == 0) {
            do_info.value = strtol(argv[3], NULL, 16);
        } else {
            do_info.value = atoi(argv[3]);
        }
    } else if (argc == 3) {
        if (strncmp(argv[2], "0x", 2) == 0 || strncmp(argv[2], "0X", 2) == 0) {
            do_info.value = strtol(argv[2], NULL, 16);
        } else {
            do_info.value = atoi(argv[2]);
        }
        do_info.channel = do_info.channel_max + 1;
    }

    printf("正在设置通道 %d 的电平至 0x%04X...\n",
            do_info.channel, do_info.value);

    if (true == is_pcie_device_enabled(device_address)) {
        printf("设备已启用\n");
    } else {
        printf("设备未启用\n");
        if (enable_pcie_device(device_address) == 0) {
            printf("设备启用成功\n");
        } else {
            printf("设备启用失败!\n");
            return EXIT_FAILURE;
        }
    }

    ret = init_pcie_mem(&ctx, base_addr, map_size);
    if (ret < 0) {
        printf("初始化内存映射失败!\n");
        return EXIT_FAILURE;
    }

    set_all_do_test(&ctx, do_info);

    cleanup_pcie_mem(&ctx);

    printf("DO 通道 %d 的测试已完成\n", do_info.channel);

    return EXIT_SUCCESS;
}
